﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace YourLastOptionsDialog.ViewModels.Options
{
    /// <summary>
    /// Converts the properties of an arbitrary object to a list of option groups, using a background thread from the thread pool.
    /// </summary>
    public class ThreadedOptionConverter
    {
        private readonly OptionsFactory _factory = new OptionsFactory();

        /// <summary>
        /// Occurs when the conversion process has completed.
        /// </summary>
        public event EventHandler<OptionConversionEventArgs> Completed;

        /// <summary>
        /// Starts the conversion on a background thread.
        /// </summary>
        /// <param name="values">The values to convert into options.</param>
        public void StartConversion(object values)
        {
            ThreadPool.QueueUserWorkItem(DoConversion, values);
        }

        private void DoConversion(object values)
        {
            try
            {
                // create options and option groups
                var options = CreateOptions(values);
                var optionGroups = CreateOptionGroups(options);
                RaiseCompletedEvent(optionGroups);
            }
            catch (Exception)
            {
                RaiseCompletedEvent(null);
            }
        }

        private IList<Option> CreateOptions(object values)
        {
            var options = new List<Option>();

            var type = values.GetType();
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var property in properties)
            {
                if (property.GetGetMethod() == null)
                {
                    // if we can't read the value, we cannot show anything to the user
                    continue;
                }

                if (property.GetSetMethod() == null)
                {
                    // there's no point in using properties that cannot be written to
                    continue;
                }

                // create option
                var option = _factory.CreateOptionFor(property, values);
                if (option != null)
                {
                    options.Add(option);
                }
            }

            return options;
        }

        private static IList<OptionGroup> CreateOptionGroups(IList<Option> options)
        {
            // first group into dictionary (a lookup won't do as we want to sort)
            var groupedOptions = options.GroupBy(o => o.GroupName)
                             .ToDictionary(o => o.Key, o => o.OrderBy(option => option.DisplayOrder).ToList());

            // create result
            var result = new List<OptionGroup>();
            foreach (var entry in groupedOptions)
            {
                // create final option group
                var optionGroup = new OptionGroup(entry.Key, entry.Value);
                result.Add(optionGroup);
            }

            return result;
        }

        private void RaiseCompletedEvent(IList<OptionGroup> optionGroups)
        {
            var handlers = Completed;
            if (handlers != null)
            {
                handlers(this, new OptionConversionEventArgs(optionGroups));
            }
        }
    }
}
